/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		acleditor save.cpp
 * PURPOSE:		acl dialog state -> acl conversion
 *				included as part of acleditor.cpp
 */

class AclEditorSave : public AclEditorLoad
{
protected:
	AclEditorSave(AclEditorInitParams const &Init) :
		AclEditorLoad(Init)
	{}

private:
	UCHAR AceTypeRevision(UCHAR Type)
	{
		if(Type <= ACCESS_MAX_MS_V2_ACE_TYPE)
			return ACL_REVISION2;
		else if(Type <= ACCESS_MAX_MS_V3_ACE_TYPE)
			return ACL_REVISION3;
		else if(Type <= ACCESS_MAX_MS_V4_ACE_TYPE)
			return ACL_REVISION4;
		else if(Type <= ACCESS_MAX_MS_V5_ACE_TYPE)
			return 5;
		else
			return MAX_ACL_REVISION;
	}

	USHORT GetExpectedAceLength(ACE const &Ace)
	{
		if(IsStdAce(Ace.Type))
			return narrowing_cast<USHORT>(FIELD_OFFSET(ACE, Standard.SidStart) + GetSidLength(Ace.Standard.SidStart));
		
		if(IsObjectAce(Ace.Type))
		{
			USHORT Ct = FIELD_OFFSET(ACE, Object.Body);

			if(Ace.Object.Flags & ACE_OBJECT_TYPE_PRESENT)
				Ct += sizeof(GUID);

			if(Ace.Object.Flags & ACE_INHERITED_OBJECT_TYPE_PRESENT)
				Ct += sizeof(GUID);

			SID const *Sid = reinterpret_cast<SID *>(reinterpret_cast<ULONG_PTR>(&Ace) + Ct);

			Ct = narrowing_cast<USHORT>(Ct + GetSidLength(Sid));

			return Ct;
		}

		return sizeof(SDE_ACE_HEADER);
	}

	USHORT GetExpectedAceLength(UCHAR Type, SID *Sid, GUID *TypeGuid, GUID *InhTypeGuid)
	{
		if(IsStdAce(Type))
			return narrowing_cast<USHORT>(FIELD_OFFSET(ACE, Standard.SidStart) + GetSidLength(Sid));
		
		if(IsObjectAce(Type))
		{
			USHORT Ct = FIELD_OFFSET(ACE, Object.Body);

			if(TypeGuid)
				Ct += sizeof(GUID);

			if(InhTypeGuid)
				Ct += sizeof(GUID);

			Ct = narrowing_cast<USHORT>(Ct + GetSidLength(Sid));

			return Ct;
		}

		return sizeof(SDE_ACE_HEADER);
	}

	auto_take<ACE> ConvertAce(ACE const *Src, ACCESS_MASK Mask, UCHAR Type, USHORT Flags, SID *Sid, GUID *TypeGuid, GUID *InhTypeGuid)
	{		
		if((TypeGuid || InhTypeGuid) && !IsObjectAce(Type))
		{
			OpText Ot(L"Trying to have a GUID on a non-object ACE");
			RaiseWin32Error(ERROR_INVALID_ACL, TRUE);
			Ot;
		}

		USHORT SrcAceExpLength = Src ? GetExpectedAceLength(*Src) : 0;
		USHORT ExtraLength = Src ? Src->Size - SrcAceExpLength : 0;
		PVOID SrcExtra = Src ? reinterpret_cast<PVOID>(reinterpret_cast<ULONG_PTR>(Src) + SrcAceExpLength) : NULL;

		USHORT NewExpLength = GetExpectedAceLength(Type, Sid, TypeGuid, InhTypeGuid);
		USHORT NewLength = NewExpLength + ExtraLength;

		auto_szpod<ACE> NewAce = auto_szpod_empty(NewLength);

		PVOID NewExtra = reinterpret_cast<PVOID>(reinterpret_cast<ULONG_PTR>(NewAce.get()) + NewExpLength);

		NewAce->Size = NewLength;
		NewAce->Type = Type;
		NewAce->Flags = (Flags & 0x00FF) | (Src ? (Src->Flags & 0xFF00) : 0); //merge editable flags with previous state of uneditable flags (if there was a previous state)
		NewAce->Mask = Mask;
		NewAce->Source = Src ? Src->Source : 0;

		if(IsStdAce(Type))
			RtlCopyMemory(NewAce->Standard.SidStart, Sid, GetSidLength(Sid));
		else if(IsObjectAce(Type))
		{
			ULONG_PTR Ct = reinterpret_cast<ULONG_PTR>(NewAce->Object.Body);

			if(TypeGuid)
			{
				NewAce->Object.Flags |= ACE_OBJECT_TYPE_PRESENT;
				*reinterpret_cast<GUID *>(Ct) = *TypeGuid;
				Ct += sizeof(GUID);
			}

			if(InhTypeGuid)
			{
				NewAce->Object.Flags |= ACE_INHERITED_OBJECT_TYPE_PRESENT;
				*reinterpret_cast<GUID *>(Ct) = *InhTypeGuid;
				Ct += sizeof(GUID);
			}

			RtlCopyMemory(reinterpret_cast<SID *>(Ct), Sid, GetSidLength(Sid));
		}

		if(SrcExtra && ExtraLength)
			RtlCopyMemory(NewExtra, SrcExtra, ExtraLength);

		return NewAce.release();
	}

	void SetColumnBitState(UCHAR i, BYTE &aceflags, ACCESS_MASK &mask, BOOL value)
	{
		if(i < COL_MASK) //must be an aceflag
			aceflags |= (value & 1) << (i - COL_ACE_FLAGS);
		else
			mask |= (value & 1) << (i - COL_MASK);
	}

	void WriteStandardBitsRow(ACCESS_MASK &Mask, UCHAR &Flags, ULONG RowIndex)
	{
		Mask = 0;
		Flags = 0;

		for(UCHAR i = COL_ACE_FLAGS; i <= COL_MAX_ACE; i++)
		{
			BOOL val = RaiseSendMessageListBox(Columns[i].Wnd, LB_GETSEL, RowIndex, NULL) ? 1 : 0;

			SetColumnBitState(i, Flags, Mask, val);
		}
	}

protected:
	void SaveAcl(OUT ACL_AND_STATE &AclAndState)
	{
		AclAndState.State = ACL_FLAG_PRESENT | ACL_FLAG_CHANGED | //plus bits not stored in the gui
			(PreviousAclState & ~(ACL_FLAG_DEFAULTED | ACL_FLAG_PROTECTED | ACL_FLAG_AUTO_INHERIT | ACL_FLAG_UNTRUSTED | ACL_FLAG_MERGE_UNPROTECT | ACL_MERGE_MODE_BITS));
		AclAndState.Acl.reset();

		auto_szpod<ACL> &Acl = AclAndState.Acl;

		if(RowMax == MAXUSHORT) //zero ACEs
		{
			Acl.realloc_ct(1);
			Acl->AclRevision = MIN_ACL_REVISION; //2
			Acl->AclSize = sizeof(ACL);
			Acl->AceCount = 0;
		}
		else
		{
			UCHAR AclRevision = MIN_ACL_REVISION;
			USHORT AclLength = sizeof(ACL);

			auto_array<auto_szpod<ACE> > Aces(RowMax + 1);

			for(USHORT i = 0; i <= RowMax; i++)
			{
				SyncAceSelection(i); //so if errors occur, the offending ace is highlighted

				UCHAR Type;
				auto_szpod<WCHAR> Text;
				auto_szpod<SID> Sid;
				GUID *TypeGuid = NULL, *InhTypeGuid = NULL;
				
				Text = GetNewListBoxText(Columns[COL_ACE_TYPE].Wnd, i);
				Type = GetAceTypeIndex(Text);

				//sid
				Text = GetNewListBoxText(Columns[COL_TSTE_SID].Wnd, i);
				Sid = TextToSid(Text);

				//type guid
				TypeGuid = Rows[i]->TypeGuid;

				//inherit type guid
				InhTypeGuid = Rows[i]->InhTypeGuid;

				ACCESS_MASK Mask;
				UCHAR Flags;

				WriteStandardBitsRow(Mask, Flags, i);

				Aces[i] = ConvertAce(Rows[i]->OriginalAce, Mask, Type, Flags, Sid, TypeGuid, InhTypeGuid);
				
				AclRevision = max(AclRevision, AceTypeRevision(Aces[i]->Revision));
				AclLength = narrowing_cast<USHORT>(AclLength + Aces[i]->Size);
			}

			auto_buffer AclBuffer;
			AclBuffer.increase_maxct(AclLength);

			{
				ACL AclHeader;
				AclHeader.Sbz1 = 1; //sd edit format ACL
				AclHeader.Sbz2 = 0;
				AclHeader.AclRevision = AclRevision;
				AclHeader.AceCount = RowMax + 1;
				AclHeader.AclSize = AclLength;

				AclBuffer.append(AclHeader);
			}

			ULONG_PTR Ct = reinterpret_cast<ULONG_PTR>(Acl.get());
			Ct += sizeof(ACL);

			//append aces into acl
			for(USHORT i = 0; i <= RowMax; i++)
				AclBuffer.append_szpod(Aces[i]);

			Acl = AclBuffer.release_as<ACL>();
		}

		if(SendMessage(Toolbar, TB_ISBUTTONCHECKED, TbDefaulted, 0))
			AclAndState.State |= ACL_FLAG_DEFAULTED;

		if(SendMessage(Toolbar, TB_ISBUTTONCHECKED, TbProtected, 0))
			AclAndState.State |= ACL_FLAG_PROTECTED;

		if(SendMessage(Toolbar, TB_ISBUTTONCHECKED, TbAutoInherit, 0))
			AclAndState.State |= ACL_FLAG_AUTO_INHERIT;

		if(SendMessage(Toolbar, TB_ISBUTTONCHECKED, TbMergeUnprotect, 0))
			AclAndState.State |= ACL_FLAG_MERGE_UNPROTECT;

		//set merge mode
		for(UCHAR i = TbMergeStart; i <= TbMergeEnd; i++)
			if(SendMessage(Toolbar, TB_ISBUTTONCHECKED, i, 0))
			{
				AclAndState.State |= (i - TbMergeStart) << ACL_MERGE_MODE_SHIFT;
				break;				
			}
	}
};